1 module serve; 2 3 import arsd.cgi; 4 import std.stdio; 5 import std.file; 6 import std.string; 7 import std.process; 8 9 // -Wl,--export=__heap_base 10 11 // https://github.com/skoppe/wasm-sourcemaps 12 13 enum DEFAULT_PATH="build"; 14 private __gshared string startPath; 15 16 string extendedContentTypeFromFileExtension(string thing) 17 { 18 string ret = contentTypeFromFileExtension(thing); 19 if(ret != null) 20 return ret; 21 22 if(ret.endsWith(".ogg")) 23 return "application/ogg"; 24 if(ret.endsWith(".mp4")) 25 return "application/mp4"; 26 27 return "application/octet-stream"; 28 } 29 30 import core.thread; 31 import core.sync.mutex; 32 void pushWebsocketMessage(string message) 33 { 34 synchronized 35 { 36 foreach(ref conn; connections) 37 conn.messages~= message; 38 } 39 // socket.send(message); 40 // synchronized websocketMessages~= message; 41 } 42 43 struct Connection 44 { 45 size_t id; 46 string[] messages; 47 } 48 private __gshared size_t id; 49 private __gshared Connection*[] connections; 50 51 void serveGameFiles(Cgi cgi) 52 { 53 import std.path; 54 string targetPath = buildNormalizedPath(startPath, DEFAULT_PATH); 55 string contentType = extendedContentTypeFromFileExtension(cgi.pathInfo); 56 string file = buildNormalizedPath(targetPath, cgi.pathInfo[1..$]); 57 58 if(cgi.websocketRequested()) 59 { 60 auto socket = cgi.acceptWebsocket(); 61 Connection conn = Connection(id); 62 synchronized 63 { 64 connections~= &conn; 65 id++; 66 } 67 enum __updateMsg = 0xff; 68 auto msg = socket.recv(); 69 while(msg.opcode != WebSocketOpcode.close) 70 { 71 synchronized 72 { 73 foreach(socketMsg; conn.messages) 74 socket.send(socketMsg); 75 conn.messages.length = 0; 76 } 77 msg = socket.recv(); 78 } 79 80 synchronized 81 { 82 socket.close(); 83 import std.algorithm; 84 ptrdiff_t index = countUntil!((Connection* c) => c.id == conn.id)(connections); 85 if(index != -1) 86 { 87 connections = connections[0..index]~ connections[index+1..$]; 88 } 89 } 90 writeln("AutoReloading WebSocket[", conn.id, "] closed."); 91 } 92 else if(cgi.pathInfo == "/") 93 { 94 string indexHTML = buildNormalizedPath(targetPath, "index.html"); 95 if(exists(indexHTML)) 96 { 97 cgi.write(readText(indexHTML)~"<script> "~import("reload_server.js")~"</script>", true); 98 } 99 else 100 { 101 // index 102 string html = "<html><head><title>Hipreme Engine Webassembly Server</title></head><body><ul>"; 103 foreach(string name; dirEntries(targetPath, SpanMode.shallow)) 104 { 105 name = name[targetPath.length..$]; 106 html ~= "<li><a href=\"" ~ name ~ "\">" ~ name ~"</a></li>"; 107 } 108 html~= "</body></html>"; 109 cgi.write(html, true); 110 111 } 112 } 113 else if(contentType) 114 { 115 if(!exists(file)) 116 { 117 cgi.setResponseStatus("404 file not found"); 118 } 119 else 120 { 121 cgi.setResponseContentType(contentType); 122 cgi.setResponseStatus(200); 123 if(contentType[0.."text".length] == "text") 124 cgi.write(readText(file)); 125 else 126 cgi.write(read(file)); 127 writeln("GET ", file, " 200"); 128 } 129 } 130 131 } 132 133 private RequestServer server; 134 /++ 135 This is the function [GenericMain] calls. View its source for some simple boilerplate you can copy/paste and modify, or you can call it yourself from your `main`. 136 Params: 137 fun = Your request handler 138 CustomCgi = a subclass of Cgi, if you wise to customize it further 139 maxContentLength = max POST size you want to allow 140 args = command-line arguments 141 History: 142 Documented Sept 26, 2020. 143 +/ 144 145 146 147 void hipengineCgiMain(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength) 148 (string[] args, string servePath) if(is(CustomCgi : Cgi)) 149 { 150 startPath = servePath; 151 if(tryAddonServers(args)) 152 return; 153 if(trySimulatedRequest!(fun, CustomCgi)(args)) 154 return; 155 156 // you can change the port here if you like 157 server.listeningPort = 9000; 158 159 string host = server.listeningHost; 160 if(host == "") host = "localhost"; 161 writeln("HipremeEngine Dev Server listening from ", host,":",server.listeningPort); 162 163 // then call this to let the command line args override your default 164 server.configureFromCommandLine(args); 165 166 167 // and serve the request(s). 168 server.serve!(fun, CustomCgi, maxContentLength)(); 169 170 } 171 172 void stopServer() 173 { 174 import core.stdc.stdlib; 175 pushWebsocketMessage("close"); 176 exit(0); 177 }